package study.translation.interpreter;

import study.translation.parser.Lexem;
import study.translation.parser.ParseTree;
import study.translation.parser.ParseTree.InnerNode;
import study.translation.parser.ParseTree.LeafNode;
import study.translation.parser.ParseTree.Node;
import study.translation.parser.ParseTree.NodeList.NodeListIterator;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Stack;

public class SimpleInterpreter implements Interpreter
{
    private PrintWriter out;

    private Stack<ExecutionContext> context = new Stack<ExecutionContext>();

    private GlobalContext global;

    public SimpleInterpreter( OutputStream outputStream )
    {
        this( new PrintWriter( outputStream ) );
    }

    public SimpleInterpreter( PrintWriter out )
    {
        this.out = out;
    }

    public void interpret( ParseTree tree ) throws InterpretExcepton
    {
        context.clear();
        context.push( new ExecutionContext() );
        global = new GlobalContext();
        program( tree.getRoot() );
    }

    protected void program( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        programHead( i.nextInnerNode() );
        variableList( i.nextInnerNode() );
        block( i.nextInnerNode() );
    }

    protected void programHead( InnerNode node ) throws InterpretExcepton
    {
        if( node.getChildNodes().size() > 1 )
        {
            NodeListIterator i = node.getChildNodes().iterator();
            subroutine( i.nextInnerNode() );
            programHead( i.nextInnerNode() );
        }
    }

    protected void variableList( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        if( i.hasNext() )
        {
            i.nextLeaf();
            variableDeclaration( i.nextInnerNode() );
            variableListTail( i.nextInnerNode() );
        }
    }

    protected void variableListTail( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        switch( i.nextLeaf().getLexem().getType() )
        {
            case COMMA:
                variableDeclaration( i.nextInnerNode() );
                variableListTail( i.nextInnerNode() );
                break;
        }
    }

    protected void variableDeclaration( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        Variable.Type type = type( i.nextInnerNode() );
        LeafNode leaf = i.nextLeaf();
        String name = leaf.getLexem().getContents();
        ExecutionContext context = this.context.peek();
        if( context.variableExists( name ) )
        {
            throw new InterpretExcepton( "Variable \"" + name + "\" is already registered (see line " +
                                         leaf.getLineNumber() + ")" );
        }
        i.nextLeaf(); // (
        Number value = arithmeticExprestion( i.nextInnerNode() );
        if( ( type == Variable.Type.REAL ) && ( value instanceof ComplexNumber ) )
        {
            throw new InterpretExcepton( "Cannot cast complex to decimal at line "
                                         + ( ( LeafNode )node.getChildNode( 1 ) ).getLineNumber() );
        }
        if( ( type == Variable.Type.COMPLEX ) && !( value instanceof ComplexNumber ) )
        {
            value = new ComplexNumber( ( RealNumber )value );
        }
        context.registerVariable( new Variable( name, type, value ) );
    }

    protected void block( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        if( i.hasNext() )
        {
            statement( i.nextInnerNode() );
            block( i.nextInnerNode() );
        }
    }

    protected void statement( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        InnerNode child = i.nextInnerNode();
        String name = child.getSymbol().getName();
        if( name.startsWith( "print" ) )
        {
            print( child );
        }
        else if( name.startsWith( "check" ) )
        {
            check( child );
        }
        else if( name.startsWith( "loop" ) )
        {
            loop( child );
        }
        else if( name.startsWith( "run" ) )
        {
            run( child );
        }
    }

    protected void run( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        LeafNode leaf = i.nextLeaf(); // run
        String name = subroutineName( i.nextInnerNode() );
        if( !global.subroutineExists( name ) )
        {
            throw new InterpretExcepton( "Subroutine \"" + name + "\" doesn't exists (see line "
                                         + leaf.getLineNumber() + ")" );
        }
        Subroutine sub = global.getSubroutine( name );
        runTail( i.nextInnerNode(), sub );
        context.push( sub.getContext() );
        variableList( sub.getVariablesNode() );
        block( sub.getBlockNode() );
        context.pop();
    }

    protected void runTail( InnerNode node, Subroutine sub ) throws InterpretExcepton
    {
        Iterator<Argument> arguments = sub.getArguments().iterator();
        NodeListIterator i = node.getChildNodes().iterator();
        LeafNode leaf = i.nextLeaf();
        if( Lexem.Type.WITH == leaf.getLexem().getType() )
        {
            parameter( i.nextInnerNode(), sub, arguments, leaf.getLineNumber() );
        }
        if( arguments.hasNext() )
        {
            throw new InterpretExcepton( "Wrong number of actual parameters for subroutine \""
                                         + sub.getName()
                                         + "\" at line " + leaf.getLineNumber()
                                         + " (" + sub.getArgumentCount() + " required)" );
        }
    }

    protected void parametersTail( InnerNode node, Subroutine sub, Iterator<Argument> arguments ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        LeafNode leaf = i.nextLeaf();
        if( Lexem.Type.COMMA == leaf.getLexem().getType() )
        {
            parameter( i.nextInnerNode(), sub, arguments, leaf.getLineNumber() );
        }
    }

    protected void parameter( InnerNode node, Subroutine sub, Iterator<Argument> arguments, int line ) throws InterpretExcepton
    {
        if( !arguments.hasNext() )
        {
            throw new InterpretExcepton( "Wrong number of actual parameters for subroutine \""
                                         + sub.getName()
                                         + "\" at line " + line +
                                         " (" + sub.getArgumentCount() + " required)" );
        }
        NodeListIterator i = node.getChildNodes().iterator();
        Number value = arithmeticExprestion( i.nextInnerNode() );
        Argument argument = arguments.next();
        if( ( argument.getType() == Variable.Type.REAL ) && ( value instanceof ComplexNumber ) )
        {
            throw new InterpretExcepton( "Cannot cast complex to decimal at line " + line );
        }
        if( ( argument.getType() == Variable.Type.COMPLEX ) && !( value instanceof ComplexNumber ) )
        {
            value = new ComplexNumber( ( RealNumber )value );
        }
        argument.setValue( value );
        parametersTail( i.nextInnerNode(), sub, arguments );
    }

    protected void subroutine( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        Lexem lexem = i.nextLeaf().getLexem(); // subroutine
        String name = subroutineName( i.nextInnerNode() );
        if( global.subroutineExists( name ) )
        {
            throw new InterpretExcepton( "Subroutine \"" + name + "\" is already exists (see line "
                                         + lexem.getBegin().getLine() + ")" );
        }
        ExecutionContext context = new ExecutionContext();
        Subroutine sub = new Subroutine( name, context );
        argumentList( i.nextInnerNode(), sub );
        sub.setVariablesNode( i.nextInnerNode() );
        sub.setBlockNode( i.nextInnerNode() );
        global.registerSubroutine( sub );
    }

    protected String subroutineName( InnerNode node ) throws InterpretExcepton
    {
        return ( ( LeafNode )node.getChildNode( 0 ) ).getLexem().getContents();
    }

    protected void argumentList( InnerNode node, Subroutine sub ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        if( i.hasNext() )
        {
            i.nextLeaf(); // arguments
            argument( i.nextInnerNode(), sub );
            argumentsTail( i.nextInnerNode(), sub );
        }
    }

    protected void argumentsTail( InnerNode node, Subroutine sub ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        Lexem lexem = i.nextLeaf().getLexem();
        if( lexem.getType() == Lexem.Type.COMMA )
        {
            argument( i.nextInnerNode(), sub );
            argumentsTail( i.nextInnerNode(), sub );
        }
    }

    public void argument( InnerNode node, Subroutine sub ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        Variable.Type type = type( i.nextInnerNode() );
        String name = i.nextLeaf().getLexem().getContents();
        Argument a = new Argument( name, type );
        sub.addArgument( a );
        sub.getContext().registerVariable( a );
    }

    protected void loop( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        Lexem loop = i.nextLeaf().getLexem();
        Lexem identifier = i.nextLeaf().getLexem();
        Variable counter = context.peek().getVariable( identifier.getContents() );
        if( null == counter )
        {
            throw new InterpretExcepton( "Variable \"" + identifier.getContents()
                                         + "\" at line " + identifier.getBegin().getLine()
                                         + " is not defined" );
        }
        if( counter.getValue() instanceof ComplexNumber )
        {
            throw new InterpretExcepton( "Loop counter should be decimal"
                                         + " at line " + identifier.getBegin().getLine() );
        }
        i.nextLeaf(); // from
        Number from = arithmeticExprestion( i.nextInnerNode() );
        i.nextLeaf(); // to
        Number to = arithmeticExprestion( i.nextInnerNode() );
        if( ( from instanceof ComplexNumber ) || ( to instanceof ComplexNumber ) )
        {
            throw new InterpretExcepton( "Loop should not have complex bounds"
                                         + " at line " + loop.getBegin().getLine() );
        }
        InnerNode blockNode = i.nextInnerNode();
        int begin = new Double( ( ( RealNumber )from ).getValue() ).intValue();
        int end = new Double( ( ( RealNumber )to ).getValue() ).intValue();
        for( int k = begin; k <= end; k++ )
        {
            counter.setValue( new RealNumber( k ) );
            block( blockNode );
        }
    }

    protected void print( InnerNode node ) throws InterpretExcepton
    {
        Object printable = printable( ( InnerNode )node.getChildNode( 1 ) );
        out.println( printable.toString() );
    }

    protected void check( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        i.nextLeaf();
        boolean condition = checkCondition( i.nextInnerNode() );
        checkTail( i.nextInnerNode(), condition );
    }

    protected boolean checkCondition( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        Lexem lexem = i.nextLeaf().getLexem();
        switch( lexem.getType() )
        {
            case LBRACKET:
                return booleanExpression( i.nextInnerNode() );

            case NOT:
                i.nextLeaf(); // (
                return !booleanExpression( i.nextInnerNode() );
        }
        throw new InterpretExcepton( "Unexpected symbol \"" + lexem.getType()
                                     + "\" at line " + lexem.getBegin().getLine() );
    }

    protected void checkTail( InnerNode node, boolean condition ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        InnerNode child = i.nextInnerNode();
        String name = child.getSymbol().getName();
        if( name.equals( "true and false" ) )
        {
            trueAndFalse( child, condition );
        }
        else if( name.equals( "false only" ) )
        {
            falseOnly( child, condition );
        }
        else
        {
            checkTailSimple( child, condition );
        }
    }

    protected void checkTailSimple( InnerNode node, boolean condition ) throws InterpretExcepton
    {
        if( condition )
        {
            NodeListIterator i = node.getChildNodes().iterator();
            InnerNode child = i.nextInnerNode();
            String name = child.getSymbol().getName();
            if( name.equals( "statement" ) )
            {
                statement( child );
                checkTailSimple( i.nextInnerNode(), condition );
            }
        }
    }

    protected void trueAndFalse( InnerNode node, boolean condition ) throws InterpretExcepton
    {
        if( condition )
        {
            block( ( InnerNode )node.getChildNode( 2 ) );
        }
        else
        {
            falseBlock( ( InnerNode )node.getChildNode( 3 ), condition );
        }
    }

    protected void falseBlock( InnerNode node, boolean condition ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        InnerNode child = i.nextInnerNode();
        String name = child.getSymbol().getName();
        if( name.equals( "false only" ) )
        {
            falseOnly( child, condition );
        }
    }

    protected void falseOnly( InnerNode node, boolean condition ) throws InterpretExcepton
    {
        if( !condition )
        {
            block( ( InnerNode )node.getChildNode( 2 ) );
        }
    }

    protected boolean booleanExpression( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        boolean term = logicalTerm( i.nextInnerNode() );
        return booleanExpressionExt( i.nextInnerNode(), term );
    }

    protected boolean booleanExpressionExt( InnerNode node, boolean term ) throws InterpretExcepton
    {
        if( node.hasChildNodes() )
        {
            return ( term && booleanExpression( ( InnerNode )node.getChildNode( 1 ) ) );
        }
        return term;
    }

    protected boolean logicalTerm( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        boolean relation = relation( i.nextInnerNode() );
        return logicalFactor( i.nextInnerNode(), relation );
    }

    protected boolean logicalFactor( InnerNode node, boolean relation ) throws InterpretExcepton
    {
        if( node.hasChildNodes() )
        {
            return ( relation || logicalTerm( ( InnerNode )node.getChildNode( 1 ) ) );
        }
        return relation;
    }

    protected boolean relation( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        Number n1 = arithmeticExprestion( i.nextInnerNode() );
        InnerNode child = i.nextInnerNode();
        Number n2 = arithmeticExprestion( i.nextInnerNode() );
        return relationalOperator( child, n1, n2 );
    }

    protected boolean relationalOperator( InnerNode node, Number left, Number right ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        Lexem lexem = i.nextLeaf().getLexem();
        Lexem.Type operation = lexem.getType();
        switch( operation )
        {
            case EQ:
                return left.equals( right );

            case GT:
                if( ( left instanceof ComplexNumber ) && ( right instanceof ComplexNumber ) )
                {
                    throw new InterpretExcepton( "Operation \"greater than\" cannot be appplied" +
                                                 " to two complex numbers at line " + lexem.getBegin().getLine() );
                }
                return left.greaterThan( right );

            case LT:
                return right.greaterThan( left );

            case GE:
                if( ( left instanceof ComplexNumber ) && ( right instanceof ComplexNumber ) )
                {
                    throw new InterpretExcepton( "Operation \"greater or equals\" cannot be appplied" +
                                                 " to two complex numbers at line " + lexem.getBegin().getLine() );
                }
                return left.greaterThan( right ) || left.equals( right );

            case LE:
                return right.greaterThan( left ) || right.equals( left );
        }
        throw new InterpretExcepton( "Unknown relational operation \"" + operation
                                     + "\" at line " + lexem.getBegin().getLine() );
    }

    protected Object printable( InnerNode node ) throws InterpretExcepton
    {
        Node child = node.getChildNodes().get( 0 );
        if( child instanceof LeafNode )
        {
            return ( ( LeafNode )child ).getLexem().getContents();
        }
        else
        {
            return arithmeticExprestion( ( InnerNode )child );
        }
    }

    protected Number arithmeticExprestion( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        InnerNode child = i.nextInnerNode();
        String name = child.getSymbol().getName();
        if( name.startsWith( "real" ) )
        {
            return realPart( child );
        }
        else if( name.startsWith( "imaginary" ) )
        {
            return imaginaryPart( child );
        }
        else
        {
            Number term = term( child );
            Number ext = arithmeticExprestionExt( i.nextInnerNode() );
            return ( null != ext ) ? term.add( ext ) : term;
        }
    }

    protected Number arithmeticExprestionExt( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        if( i.hasNext() )
        {
            Lexem.Type sign = i.nextLeaf().getLexem().getType();
            Number number = arithmeticExprestion( i.nextInnerNode() );
            switch( sign )
            {
                case PLUS:
                    return number;
                case MINUS:
                    return number.changeSign();
            }
        }
        return null;
    }

    protected Number term( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        Number factor = factor( i.nextInnerNode() );
        return termExt( i.nextInnerNode(), factor );
    }

    protected Number factor( InnerNode node ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        Lexem lexem = i.nextLeaf().getLexem();
        switch( lexem.getType() )
        {
            case IDENTIFIER:
                Variable var = context.peek().getVariable( lexem.getContents() );
                if( null == var )
                {
                    throw new InterpretExcepton( "Variable \"" + lexem.getContents()
                                                 + "\" at line " + lexem.getBegin().getLine()
                                                 + " is not defined" );
                }
                return var.getValue();

            case NUMBER:
                return NumberParser.parse( lexem.getContents() );

            case LBRACKET:
                return arithmeticExprestion( i.nextInnerNode() );

            case MINUS:
                return factor( i.nextInnerNode() ).changeSign();
        }
        throw new InterpretExcepton( "Unknown symbol \"" + lexem.getType() + "\" at line " + lexem.getBegin().getLine() );
    }

    protected Number termExt( InnerNode node, Number factor ) throws InterpretExcepton
    {
        NodeListIterator i = node.getChildNodes().iterator();
        if( i.hasNext() )
        {
            Lexem.Type operator = i.nextLeaf().getLexem().getType();
            Number term = term( i.nextInnerNode() );
            switch( operator )
            {
                case STAR:
                    return factor.mul( term );

                case SLASH:
                    return factor.div( term );
            }
        }
        return factor;
    }

    protected RealNumber realPart( InnerNode node ) throws InterpretExcepton
    {
        Number expr = arithmeticExprestion( ( InnerNode )node.getChildNode( 2 ) );
        return ( expr instanceof ComplexNumber )
               ? ( ( ComplexNumber )expr ).getRe()
               : ( RealNumber )expr;
    }

    protected RealNumber imaginaryPart( InnerNode node ) throws InterpretExcepton
    {
        Number expr = arithmeticExprestion( ( InnerNode )node.getChildNode( 2 ) );
        return ( expr instanceof ComplexNumber )
               ? ( ( ComplexNumber )expr ).getIm()
               : new RealNumber( 0 );
    }

    protected Variable.Type type( InnerNode node ) throws InterpretExcepton
    {
        Lexem lexem = ( ( LeafNode )node.getChildNode( 0 ) ).getLexem();
        switch( lexem.getType() )
        {
            case COMPLEX:
                return Variable.Type.COMPLEX;
            case DECIMAL:
                return Variable.Type.REAL;
        }
        throw new InterpretExcepton( "Unsupported type of variable: \""
                                     + lexem.getType() + "\" at line " + lexem.getBegin().getLine() );
    }
}
